// Package network provides network optimization features for on-demand loading
package network

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/smart-snapshotter/internal/logging"
	"github.com/smart-snapshotter/pkg/storage"
)

// NetworkOptimizer provides network optimization services
type NetworkOptimizer struct {
	config          *NetworkConfig
	downloadManager *DownloadManager
	parallelDL      *ParallelDownloader
	resumeManager   *ResumeManager
	logger          logging.Logger
	wg              sync.WaitGroup
	ctx             context.Context
	cancel          context.CancelFunc
}

// NetworkConfig contains network optimizer configuration
type NetworkConfig struct {
	DownloadManager  *DownloadManagerConfig  `yaml:"download_manager"`
	ParallelDownload *ParallelConfig         `yaml:"parallel_download"`
	Resume           *ResumeConfig           `yaml:"resume"`
}

// DownloadConfig contains download manager configuration
type DownloadConfig struct {
	MaxParallelDownloads int           `yaml:"max_parallel_downloads"`
	ChunkSize           int64          `yaml:"chunk_size"`
	MaxRetries          int            `yaml:"max_retries"`
	RetryDelay          time.Duration  `yaml:"retry_delay"`
	BandwidthLimit      int64          `yaml:"bandwidth_limit"` // bytes per second
	UserAgent           string         `yaml:"user_agent"`
	Timeout             time.Duration  `yaml:"timeout"`
	ResumeEnabled       bool           `yaml:"resume_enabled"`
	VerifyChecksum      bool           `yaml:"verify_checksum"`
	TempDir             string         `yaml:"temp_dir"`
}

// DefaultConfig returns default network configuration
func DefaultConfig() *NetworkConfig {
	return &NetworkConfig{
		DownloadManager: &DownloadManagerConfig{
			MaxParallelDownloads: 5,
			ChunkSize:           1024 * 1024, // 1MB
			MaxRetries:          3,
			RetryDelay:          time.Second,
			BandwidthLimit:      0, // Unlimited
			UserAgent:           "Smart-Snapshotter/1.0",
			Timeout:             30 * time.Minute,
			ResumeEnabled:       true,
			VerifyChecksum:      true,
			TempDir:             "/tmp/smart-snapshotter/downloads",
		},
		ParallelDownload: &ParallelConfig{
			MaxConnections:    4,
			ChunkSize:         1024 * 1024, // 1MB
			MinChunkSize:      64 * 1024,   // 64KB
			MaxChunkSize:      10 * 1024 * 1024, // 10MB
			RetryAttempts:     3,
			RetryDelay:        time.Second,
			ConnectionTimeout: 30 * time.Second,
		},
		Resume: DefaultResumeConfig(),
	}
}

// NewNetworkOptimizer creates a new network optimizer
func NewNetworkOptimizer(config *NetworkConfig, logger logging.Logger) (*NetworkOptimizer, error) {
	if config == nil {
		config = DefaultConfig()
	}

	ctx, cancel := context.WithCancel(context.Background())

	no := &NetworkOptimizer{
		config: config,
		logger: logger,
		ctx:    ctx,
		cancel: cancel,
	}

	// Initialize download manager
	dmConfig := &DownloadManagerConfig{
		MaxParallelDownloads: config.DownloadManager.MaxParallelDownloads,
		ChunkSize:          config.DownloadManager.ChunkSize,
		MaxRetries:         config.DownloadManager.MaxRetries,
		RetryDelay:         config.DownloadManager.RetryDelay,
		BandwidthLimit:     config.DownloadManager.BandwidthLimit,
		UserAgent:          config.DownloadManager.UserAgent,
		Timeout:            config.DownloadManager.Timeout,
		ResumeEnabled:      config.DownloadManager.ResumeEnabled,
		VerifyChecksum:     config.DownloadManager.VerifyChecksum,
		TempDir:            config.DownloadManager.TempDir,
	}

	downloadManager, err := NewDownloadManager(dmConfig, logger)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to create download manager: %w", err)
	}
	no.downloadManager = downloadManager

	// Initialize parallel downloader
	parallelDownloader := NewParallelDownloader(config.ParallelDownload, logger)
	no.parallelDL = parallelDownloader

	// Initialize resume manager
	resumeManager, err := NewResumeManager(config.Resume.StateDir, logger)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to create resume manager: %w", err)
	}
	no.resumeManager = resumeManager

	return no, nil
}

// Start starts the network optimizer
func (no *NetworkOptimizer) Start() error {
	no.logger.Info("Starting network optimizer")

	// Start download manager
	if err := no.downloadManager.Start(); err != nil {
		return fmt.Errorf("failed to start download manager: %w", err)
	}

	// Start resume cleanup worker
	if no.config.Resume.Enabled {
		no.wg.Add(1)
		go no.resumeCleanupWorker()
	}

	return nil
}

// Stop stops the network optimizer
func (no *NetworkOptimizer) Stop() error {
	no.logger.Info("Stopping network optimizer")
	no.cancel()

	// Stop download manager
	if err := no.downloadManager.Stop(); err != nil {
		no.logger.Error("Failed to stop download manager", StringField("error", err.Error()))
	}

	// Wait for workers
	no.wg.Wait()

	return nil
}

// DownloadFile downloads a file with all optimizations
func (no *NetworkOptimizer) DownloadFile(ctx context.Context, url, filePath string, checksum string) (*Download, error) {
	no.logger.Info("Starting optimized file download", StringField("url", url), StringField("filePath", filePath))

	// Check if we can resume
	if no.config.Resume.Enabled {
		// Create temporary download for resume check
		tempDownload := &Download{
			ID:       generateDownloadID(url),
			URL:      url,
			FilePath: filePath,
			Checksum: checksum,
		}

		canResume, state := no.resumeManager.CanResume(tempDownload)
		if canResume && state != nil {
			no.logger.Info("Resuming download", StringField("downloadID", tempDownload.ID))
			if err := no.resumeManager.ResumeDownload(tempDownload); err == nil {
				// Start parallel download with resume
				if err := no.parallelDL.Download(ctx, tempDownload, no.downloadManager.bandwidthLimiter); err == nil {
					return tempDownload, nil
				}
			}
			no.logger.Warn("Failed to resume download, starting fresh", StringField("downloadID", tempDownload.ID))
		}
	}

	// Start fresh download
	download, err := no.downloadManager.Download(ctx, url, filePath, checksum)
	if err != nil {
		return nil, fmt.Errorf("failed to start download: %w", err)
	}

	// Use parallel downloader if enabled
	if err := no.parallelDL.Download(ctx, download, no.downloadManager.bandwidthLimiter); err != nil {
		return download, fmt.Errorf("parallel download failed: %w", err)
	}

	return download, nil
}

// GetDownloadProgress returns download progress
func (no *NetworkOptimizer) GetDownloadProgress(downloadID string) (*DownloadProgress, error) {
	return no.downloadManager.GetProgress(downloadID)
}

// GetNetworkStats returns network statistics
func (no *NetworkOptimizer) GetNetworkStats() NetworkStats {
	dmStats := no.downloadManager.GetStats()

	return NetworkStats{
		TotalDownloads:     dmStats.TotalDownloads,
		ActiveDownloads:    dmStats.ActiveDownloads,
		CompletedDownloads: dmStats.CompletedDownloads,
		FailedDownloads:    dmStats.FailedDownloads,
		TotalBytes:         dmStats.TotalBytes,
		DownloadedBytes:    dmStats.DownloadedBytes,
		AverageSpeed:       dmStats.AverageSpeed,
	}
}

// SetBandwidthLimit updates the bandwidth limit
func (no *NetworkOptimizer) SetBandwidthLimit(bytesPerSecond int64) {
	no.downloadManager.bandwidthLimiter.SetLimit(bytesPerSecond)
	no.logger.Info("Updated bandwidth limit", StringField("limit", fmt.Sprintf("%d", bytesPerSecond)))
}

// GetBandwidthLimit returns the current bandwidth limit
func (no *NetworkOptimizer) GetBandwidthLimit() int64 {
	return no.downloadManager.bandwidthLimiter.GetLimit()
}

// PauseDownload pauses a download
func (no *NetworkOptimizer) PauseDownload(downloadID string) error {
	download, ok := no.downloadManager.GetDownload(downloadID)
	if !ok {
		return fmt.Errorf("download not found: %s", downloadID)
	}

	download.mu.Lock()
	if download.Status == "downloading" {
		download.Status = "paused"
		download.cancel()

		// Save state for resume
		if no.config.Resume.Enabled {
			state := no.resumeManager.CreateDownloadState(download)
			if err := no.resumeManager.SaveDownloadState(state); err != nil {
				no.logger.Warn("Failed to save download state", StringField("downloadID", downloadID), StringField("error", err.Error()))
			}
		}
	}
	download.mu.Unlock()

	return nil
}

// ResumeDownload resumes a paused download
func (no *NetworkOptimizer) ResumeDownload(downloadID string) error {
	download, ok := no.downloadManager.GetDownload(downloadID)
	if !ok {
		return fmt.Errorf("download not found: %s", downloadID)
	}

	download.mu.Lock()
	if download.Status == "paused" {
		// Create new context
		ctx, cancel := context.WithCancel(no.ctx)
		download.ctx = ctx
		download.cancel = cancel
		download.Status = "downloading"
		download.mu.Unlock()

		// Resume download in background
		go func() {
			if err := no.parallelDL.Download(ctx, download, no.downloadManager.bandwidthLimiter); err != nil {
				no.logger.Error("Failed to resume download", StringField("downloadID", downloadID), StringField("error", err.Error()))
			}
		}()
	} else {
		download.mu.Unlock()
	}

	return nil
}

// CancelDownload cancels a download
func (no *NetworkOptimizer) CancelDownload(downloadID string) error {
	download, ok := no.downloadManager.GetDownload(downloadID)
	if !ok {
		return fmt.Errorf("download not found: %s", downloadID)
	}

	download.mu.Lock()
	if download.Status == "downloading" || download.Status == "paused" {
		download.Status = "cancelled"
		download.cancel()

		// Clean up resume state
		if no.config.Resume.Enabled {
			if err := no.resumeManager.DeleteDownloadState(downloadID); err != nil {
				no.logger.Warn("Failed to delete download state", StringField("downloadID", downloadID), StringField("error", err.Error()))
			}
		}
	}
	download.mu.Unlock()

	return nil
}

// resumeCleanupWorker periodically cleans up old resume states
func (no *NetworkOptimizer) resumeCleanupWorker() {
	defer no.wg.Done()

	ticker := time.NewTicker(no.config.Resume.CleanupInterval)
	defer ticker.Stop()

	for {
		select {
		case <-no.ctx.Done():
			return
		case <-ticker.C:
			if err := no.resumeManager.CleanupOldStates(no.config.Resume.MaxAge); err != nil {
				no.logger.Error("Failed to cleanup old resume states", StringField("error", err.Error()))
			}
		}
	}
}

// NetworkStats contains network optimizer statistics
type NetworkStats struct {
	TotalDownloads     int64
	ActiveDownloads    int32
	CompletedDownloads int64
	FailedDownloads    int64
	TotalBytes         int64
	DownloadedBytes    int64
	AverageSpeed       int64
}

// Integration with existing loader system
func (no *NetworkOptimizer) CreateNetworkLoader(storage storage.Backend) *NetworkLoader {
	return &NetworkLoader{
		optimizer: no,
		storage:   storage,
		logger:    no.logger,
	}
}

// NetworkLoader implements loader interface with network optimization
type NetworkLoader struct {
	optimizer *NetworkOptimizer
	storage   storage.Backend
	logger    logging.Logger
}

// Load loads data with network optimization
func (nl *NetworkLoader) Load(ctx context.Context, key string) ([]byte, error) {
	// Try storage first
	if data, err := nl.storage.LoadFile(ctx, key); err == nil {
		return data, nil
	}

	// If not found in storage, try to download
	// This assumes the key can be mapped to a URL
	// In a real implementation, you'd need URL mapping logic
	url := nl.keyToURL(key)
	if url == "" {
		return nil, fmt.Errorf("no URL mapping for key: %s", key)
	}

	tempFile := filepath.Join(nl.optimizer.config.DownloadManager.TempDir, key+".tmp")
	download, err := nl.optimizer.DownloadFile(ctx, url, tempFile, "")
	if err != nil {
		return nil, fmt.Errorf("failed to download file: %w", err)
	}

	// Wait for download to complete
	for {
		progress, err := nl.optimizer.GetDownloadProgress(download.ID)
		if err != nil {
			return nil, fmt.Errorf("failed to get download progress: %w", err)
		}

		if progress.Status == "completed" {
			break
		}
		if progress.Status == "failed" {
			return nil, fmt.Errorf("download failed: %v", progress.Error)
		}

		time.Sleep(100 * time.Millisecond)
	}

	// Read downloaded file
	data, err := ioutil.ReadFile(tempFile)
	if err != nil {
		return nil, fmt.Errorf("failed to read downloaded file: %w", err)
	}

	// Store in storage for future use
	if err := nl.storage.StoreFile(ctx, key, data); err != nil {
		nl.logger.Warn("Failed to store downloaded file", StringField("key", key), StringField("error", err.Error()))
	}

	// Clean up temp file
	os.Remove(tempFile)

	return data, nil
}

// keyToURL maps a storage key to a URL (placeholder implementation)
func (nl *NetworkLoader) keyToURL(key string) string {
	// This is a placeholder implementation
	// In a real system, you'd have proper URL mapping logic
	// based on your registry/repository configuration
	if len(key) == 64 { // Assume it's a hash
		return fmt.Sprintf("https://registry.example.com/v2/blobs/sha256:%s/data", key[:2])
	}
	return ""
}